﻿Imports System.Numerics
Imports System.Runtime.CompilerServices
Imports BigDecimals.Math
Imports System.Text.RegularExpressions

<Discardable()> <Serializable()>
Public Class BigDecimal
  Implements IDisposable

  Protected Friend Shared Internal_Limit As Integer = 1500 'New BigDecimal(20)
  Protected Shared Limit_Display As Integer = 1000
#Region "The Digit Store aka mN"
  ''' <summary>
  ''' The BigInteger into which the BigDecimal Digits are stored
  ''' </summary>
  ''' <remarks>Also contains the Sign of the BigDecimal.</remarks>
  Protected Friend Digits As New BigInteger
#End Region
#Region "Decimal Point Tracker"
  ''' <summary>
  ''' This keeps track of where the Decimal Point is.
  ''' </summary>
  ''' <remarks> 
  '''  The position start from 0 upwards.
  ''' 0 - Integer, 
  ''' 1 -> Eg 1.2. 
  ''' 2 -> Eg 1.23, 
  ''' Etc
  '''</remarks>
  Protected Friend DecimalPoint As UInteger

#End Region
#Region "Initialisers (New Subs)"
  Public Sub New()
    ' The default is Zero {0,0}
    Digits = New BigInteger(0)
    DecimalPoint = 0
  End Sub

  Private Sub New(ByVal N As BigInteger, Optional ByVal DP As UInteger = 0)
    Digits = N
    DecimalPoint = DP
  End Sub

  Public Sub New(ByVal s As String)
    If s.IsntNull Then
      Dim inst As BigDecimal = Nothing
      If BigDecimal.TryParse(s, inst) = False Then Throw New ArgumentException
      Me.Digits = inst.Digits
      Me.DecimalPoint = inst.DecimalPoint
    Else
      Throw New ArgumentNullException
    End If

  End Sub
#End Region

  Protected Friend Shared RegexForDecimal As New System.Text.RegularExpressions.Regex("^(?<Sign>\+|\-)?(?<INTEGER>[0-9]+)(?<DP>\.(?<FLOAT>[0-9]*))?$", RegexOptions.Compiled)

  Public ReadOnly Property Sign() As Integer
    Get
      Return Me.Digits.Sign
    End Get
  End Property


#Region "Shared Create"
  Public Shared Function CreateNew() As BigDecimal
    Return New BigDecimal
  End Function
  Public Shared Function CreateNew(ByVal N As BigInteger, Optional ByVal DP As UInteger = 0) As BigDecimal
    Return New BigDecimal(N, DP)
  End Function
#End Region


#Region "ToString Overrides"
  Public Overrides Function ToString() As String
    Dim rs As New System.Text.StringBuilder(Digits.ToString)
    Dim DP_FromR As Integer = CInt(rs.Length) - CInt(DecimalPoint)
    Dim rt As Integer
    If DP_FromR < 0 Then
      rt = -DP_FromR
    ElseIf DP_FromR > 0 Then
      rt = DP_FromR - 1
    End If
    Dim neg As Integer = 0
    If rs(0) = "-" Then
      neg = 1
      ' Negatives
      If DecimalPoint > 0 Then
        ' Has a Fractional Part
        If DP_FromR <= 0 Then
          rs.Insert(1, "0"c, rt + 1)
          rs.Insert(1, ".")
        Else
          rs.Insert(1, "0"c, rt)
          ' rs.Insert(DP_FromR, ".")
          rs.Insert(CInt(rs.Length - DecimalPoint), ".")

        End If
      End If
      ' Whole Number
    Else
      ' Positives
      If DecimalPoint > 0 Then
        ' Has a Fractional Part
        If DP_FromR = 0 Then
          rs.Insert(0, "0"c, rt)
          rs.Insert(rt, ".")
        ElseIf DP_FromR < 0 Then
          rs.Insert(0, "0"c, rt - DP_FromR)
          rs.Insert(rt, ".")
        ElseIf DP_FromR > 0 Then
          rs.Insert(0, "0"c, rt)
          rs.Insert(DP_FromR + rt, ".")
        End If
      End If
      ' Whole Number
    End If
    ' Make the digits after the decimal point the correct display length 
    Dim ns As String = rs.ToString
    Dim dpp As Integer = ns.IndexOf(".")
    If dpp >= 0 Then
      Dim fd As String = ns.Substring(dpp + 1)
      ns = ns.Substring(0, dpp + 1) & If(fd.Length <= BigDecimal.Limit_Display, fd, fd.Substring(0, BigDecimal.Limit_Display))
      rs = New Text.StringBuilder(ns)
    End If
    ' Remove Zero for Head of digits
    While rs.Length > 1 AndAlso rs(neg) = "0"
      rs.Remove(neg, 1)
    End While
    If rs(0) = "-" AndAlso rs(1) = "." Then rs.Insert(1, "0")
    If rs(0) = "." Then rs.Insert(0, "0")
    If rs(rs.Length - 1) = "." Then rs.Remove(rs.Length - 1, 1)
    Return rs.ToString
  End Function
#End Region


#Region "IDisposable Support"
  Private disposedValue As Boolean ' To detect redundant calls

  ' IDisposable
  Protected Overridable Sub Dispose(ByVal disposing As Boolean)
    If Not Me.disposedValue Then
      If disposing Then
        ' TODO: dispose managed state (managed objects).
        Me.Digits = Nothing

      End If

      ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
      ' TODO: set large fields to null.
    End If
    Me.disposedValue = True
  End Sub

  ' TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources.
  'Protected Overrides Sub Finalize()
  '    ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
  '    Dispose(False)
  '    MyBase.Finalize()
  'End Sub

  ' This code added by Visual Basic to correctly implement the disposable pattern.
  Public Sub Dispose() Implements IDisposable.Dispose
    ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
    Dispose(True)
    GC.SuppressFinalize(Me)
  End Sub
#End Region

End Class
Public Module e
 <Extension()>
 Public Function IsntNull(ByVal Soure As Object) As Boolean
  Return (Soure IsNot Nothing)
 End Function

End Module